-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
v1 Rewrite #34
v1 Rewrite #34
Conversation
I don't feel prepared to review this PR, but if there comes a point where you need someone to test it, let me know! I have a Giving branch configured with ESLint v9 right now. |
@markpalfreeman I'm meeting with the other Balto maintainers tomorrow morning to discuss this. If you want you can change your GitHub workflow to something like this to test on this branch: jobs:
balto-eslint:
runs-on: ubuntu-latest
steps:
- uses: planningcenter/balto-utils/yarn@v2
- uses: planningcenter/balto-eslint@ns/v1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great. Using the core
package to make annotations in STDOUT is clearly the blessed path going forward, and at the end of the day it's a lot easier to reason about when it's just STDOUT rather than API interactions.
Honestly, I really only have one big question, which is about the order-dependent API of EslintResult
. I think we can find an API that more directly acknowledges the async initialization process, and we'll be in good shape.
Also, a huge thank you for writing tests for this project. That makes the whole thing so much more viable to maintain and keep working on into the future 🍻
// Eslint will return exit code 1 if it finds linting problems, but that is | ||
// expected and we don't want to stop execution because of it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for this comment
src/eslint_result.ts
Outdated
|
||
async asyncInitialize() { | ||
this.changeRanges = await generateChangeRanges( | ||
this.resultObject.filePath, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is filePath
relative or absolute? My headspace for the question comes from noting distinction in how we build the array of changed files. Sometimes those are relative to the workingDir
vs relative to the git root. However, based on my checking locally, git diff --name-only
always outputs the path from the git root
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is a good question. After looking myself, it seems filePath
from eslint is an absolute path. Git appears to not mind that we are calling diff on an absolute path.
The file paths that we give to eslint are from the working directory though, as we manipulate the path to scope it to the working directory here:
Lines 53 to 55 in 2f523c7
.split("\n") | |
.filter((path) => path !== "") | |
.map((path) => path.replace(workingDirectory + "/", "")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice. Ok, that makes sense. Absolute file paths from eslint
seem good, and it makes sense that git
would be happy with that. Thanks for answering!
src/git_utils.ts
Outdated
.map((match) => { | ||
const rangeStart = parseInt(match!.groups!.rangeStart, 10) | ||
const rangeLength = match!.groups!.rangeLength | ||
? parseInt(match!.groups!.rangeLength) - 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
? parseInt(match!.groups!.rangeLength) - 1 | |
? parseInt(match!.groups!.rangeLength, 10) - 1 |
this.relevantMessages.forEach((msg) => { | ||
switch (msg.severity) { | ||
case 1: | ||
this.relevantWarningCount += 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The imperative nature of this class has me wondering if we could find a more imperative API generate the results we want. I wonder what it would look like if we waited to initialize the EslintResult
until we'd generated the data we care about. Maybe we could have a static async
function on EslintResult
that transforms the ResultObject
into a fully-formed EslintResult
.
It might be something like
class EslintResult {
static async function fromResult(result: ResultObject, compareSha: string) {
const changeRanges = await generateChangeRanges(result.filePath, compareSha)
const relevantMessages = result.messages.filter(m => changeRanges.some(cr => cr.doesInclude(m.line)))
const { warningCount, errorCount } = calculateCounts(relevantMessages)
return new EslintResult({ result, relevantMessages, warningCount, errorCount })
}
static function calculateCounts(messages: ResultObject['messages']) {
return messages.reduce((acc, message) => {
switch (message.severity) {
case 1:
acc.warningCount += 1
break
case 2:
acc.errorCount += 1
break
}
return acc
}, { warningCount: 0, errorCount: 0 })
}
}
We can still initialize all the objects async (with Promise.all
), but we no longer need to call instance methods in a certain order for them to be valid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that idea, I'll take a look 👍
src/eslint_result.ts
Outdated
endColumn: msg.endColumn, | ||
} | ||
switch (msg.severity) { | ||
case 1: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Purely for readability, maybe let's extract an enum for severity
enum Severity {
Warning = 1,
Error = 2
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great call 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We talked this over on a call, and I feel great about this, especially the overall simplification and the addition of tests!
Instead of constructing the object and then calling an async function on it, let's do it all in one go. The simplifies the class defintion of EslintResult a bit.
That set of changes looks solid. I'm so excited to have this released! |
With check runs, we could specify the naming ourselves. With the switch to workflow commands, it depends on how consumers of this action name the consuming action.
In this PR I rewrote the action in TypeScript and swapped Github check runs for workflow command output powered annotations. This is a culmination of a lot of internal discussion, planning, and unexpected delays.
The core motivation is this: there have been longstanding issues with Github check runs initiated from Github actions, specifically that they get associated with the incorrect action run. Github now recommends using workflow output command annotations for situations like this.
There are also other issues we wanted to address:
Breaking changes
balto-eslint
display aneutral
conclusion.conclusion-level
ofsuccess
(the default) orfailure
to control how this affects the status of Github checks in the UI.balto-eslint
for labeling to be correct)Non-breaking changes
CONTRIBUTING.md
file to a section in theREADME.md
Notes for reviewing
There's a lot here! Sorry! Everything outside of
src/
andaction.yml
are just supporting files though.This should resolve #3 and #5